#!/usr/bin/env perl
#
# Generate sudo_defs_table and associated defines
#
# Input should be formatted thusly:
#
# var_name
#	TYPE
#	description (or NULL)
#	array of struct def_values if TYPE == T_TUPLE

use warnings;
use strict;

my ($header, $cfile, $infile);

# Deal with optional -o (output) argument
if ($#ARGV > 0 && $ARGV[0] eq "-o") {
    shift;
    $header = $cfile = shift;
    $header .= '.h';
    $cfile .= '.c';
}
die "usage: $0 [input_file]\n" unless $#ARGV == -1 || $#ARGV == 0;

$infile = $ARGV[0] || "def_data.in";
if (!defined($header)) {
    $header = $infile;
    $header =~ s/(\.in)?$/.h/;
}
if (!defined($cfile)) {
    $cfile = $infile;
    $cfile =~ s/(\.in)?$/.c/;
}

open(IN, "<$infile") || die "$0: can't open $infile: $!\n";
open(HEADER, ">$header") || die "$0: can't open $header: $!\n";
open(CFILE, ">$cfile") || die "$0: can't open $cfile: $!\n";

my $count = 0;
my @tuple_values = ( "never" );
my @records = ();
my ($var, $type, $desc, $values, $callback, $field);
while (<IN>) {
    chomp;
    s/\s*#.*$//;
    next if /^\s*$/;

    if (/^\S/) {
	# Store previous record and begin new one
	$records[$count++] = [$var, $type, $desc, $values, $callback] if defined($var);

	$var = $_;
	$type = '';
	$desc = undef;
	$values = undef;
	$callback = undef;
	$field = 0;
    } else {
	$field++;
	s/^\s+//;
	s/\s+$//;
	if ($field == 1) {
	    # type
	    $type = $_;
	} elsif ($field == 2) {
	    # description
	    if ($_ eq "NULL") {
		$desc = "NULL";
	    } else {
		# Strip leading and trailing double quote and escape the rest
		s/^"//;
		s/"$//;
		s/"/\\"/g;
		$desc = "N_(\"$_\")";
	    }
	} elsif ($field == 3 || $field == 4) {
	    if (s/^\*//) {
		$callback = $_;
	    } else {
		die "$0: syntax error near line $.\n" if $type !~ /^T_TUPLE/;
		$values = [ split ];
		foreach my $v (@$values) {
		    push(@tuple_values, $v) unless grep(/^$v$/, @tuple_values);
		}
	    }
	} else {
	    die "$0: syntax error near line $.\n";
	}
    }
}
$records[$count++] = [$var, $type, $desc, $values, $callback] if defined($var);

# Print out value arrays
for (my $i = 0; $i < $count; $i++) {
    if (defined($records[$i]->[3])) {
	die "Values list specified for non-tuple\n" unless
	    $records[$i]->[1] =~ /^T_TUPLE/;
	printf CFILE "static struct def_values def_data_%s[] = {\n",
	    $records[$i]->[0];
	foreach (@{$records[$i]->[3]}) {
	    print CFILE "    { \"$_\", $_ },\n";
	}
	print CFILE "    { NULL, 0 },\n";
	print CFILE "};\n\n";
    }
}

# Print each record
print CFILE "struct sudo_defs_types sudo_defs_table[] = {\n    {\n";
for (my $i = 0; $i < $count; $i++) {
    print_record($records[$i], $i);
}
print CFILE "\tNULL, 0, NULL\n    }\n};\n";

# Print out def_tuple
if (@tuple_values) {
    print HEADER "\nenum def_tuple {\n";
    for (my $i = 0; $i <= $#tuple_values; $i++) {
	printf HEADER "\t%s%s\n", $tuple_values[$i],
	    $i != $#tuple_values ? "," : "";
    }
    print HEADER "};\n";
}

close(IN);
close(HEADER);
close(CFILE);

exit(0);

sub print_record {
    my ($rec, $recnum) = @_;
    my ($i, $v, $defname);

    # each variable gets a macro to access its value
    $defname = "I_" . uc($rec->[0]);
    printf HEADER "#define %-23s %d\n", $defname, $recnum;
    for ($rec->[1]) {
	if    (/^T_INT/)      { $v = "ival"; }
	elsif (/^T_UINT/)     { $v = "uival"; }
	elsif (/^T_STR/)      { $v = "str"; }
	elsif (/^T_FLAG/)     { $v = "flag"; }
	elsif (/^T_MODE/)     { $v = "mode"; }
	elsif (/^T_LIST/)     { $v = "list"; }
	elsif (/^T_LOGFAC/)   { $v = "ival"; }
	elsif (/^T_LOGPRI/)   { $v = "ival"; }
	elsif (/^T_TUPLE/)    { $v = "tuple"; }
	elsif (/^T_TIMESPEC/) { $v = "tspec"; }
	elsif (/^T_TIMEOUT/)  { $v = "ival"; }
	else { die "$0: unknown defaults type: $_\n"; }
    }
    printf HEADER "#define %-23s (sudo_defs_table[$defname].sd_un.${v})\n",
	"def_$rec->[0]";

    print CFILE "\t\"$rec->[0]\", $rec->[1],\n\t$rec->[2],\n";
    if (defined($rec->[3])) {
	printf CFILE "\tdef_data_$rec->[0],\n";
    } else {
	printf CFILE "\tNULL,\n";
    }
    printf CFILE "\t$rec->[4],\n" if defined($rec->[4]);
    print CFILE "    }, {\n";
}
